package cn.icanci.loopstack.rec.engine.sdk;

import java.lang.reflect.Field;
import java.util.Set;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.google.common.collect.Sets;

import cn.icanci.loopstack.rec.common.utils.FieldUtils;
import cn.icanci.loopstack.rec.engine.sdk.exception.InjectionBeanException;
import cn.icanci.loopstack.rec.engine.sdk.extensions.RecExtensionLoader;
import cn.icanci.loopstack.rec.engine.sdk.extensions.SpringBean;
import cn.icanci.loopstack.rec.engine.sdk.properties.RecProperties;
import cn.icanci.loopstack.rec.engine.sdk.spi.*;

/**
 * @author icanci
 * @since 1.0 Created in 2022/11/15 20:34
 */
@Configuration
@ComponentScan({ "cn.icanci.loopstack.rec.engine.sdk" })
@EnableConfigurationProperties(RecProperties.class)
//@ConditionalOnClass(RuleAggregationCluster.class)
@AutoConfigureBefore
public class RecEngineSDKAutoConfig implements CommandLineRunner {
    /**
     * Spring 上下文
     */
    private ApplicationContext context;

    public RecEngineSDKAutoConfig(ApplicationContext context) {
        this.context = context;
    }

    // ============================== SPI ==============================

    /**
     * domainSPI
     *
     * @return domainSPI
     */
    @Bean("domainSPI")
    public DomainSPI domainSPI() {
        DomainSPI extension = RecExtensionLoader.getExtensionLoader(DomainSPI.class).getExtension();
        injectionSpringBeanProcessor(extension);
        return extension;
    }

    /**
     * BaseDataSPI
     *
     * @return BaseDataSPI
     */
    @Bean("baseDataSPI")
    public BaseDataSPI baseDataSPI() {
        BaseDataSPI extension = RecExtensionLoader.getExtensionLoader(BaseDataSPI.class).getExtension();
        injectionSpringBeanProcessor(extension);
        return extension;
    }

    /**
     * DataSourceSPI
     *
     * @return DataSourceSPI
     */
    @Bean("dataSourceSPI")
    public DataSourceSPI dataSourceSPI() {
        DataSourceSPI extension = RecExtensionLoader.getExtensionLoader(DataSourceSPI.class).getExtension();
        injectionSpringBeanProcessor(extension);
        return extension;
    }

    /**
     * MetadataSPI
     *
     * @return 返回MetadataSPI脚本执行引擎
     */
    @Bean("metadataSPI")
    public MetadataSPI metadataSPI() {
        MetadataSPI extension = RecExtensionLoader.getExtensionLoader(MetadataSPI.class).getExtension();
        injectionSpringBeanProcessor(extension);
        return extension;
    }

    /**
     * SceneSPI
     *
     * @return SceneSPI
     */
    @Bean("sceneSPI")
    public SceneSPI sceneSPI() {
        SceneSPI extension = RecExtensionLoader.getExtensionLoader(SceneSPI.class).getExtension();
        injectionSpringBeanProcessor(extension);
        return extension;
    }

    /**
     * StrategySPI
     *
     * @return StrategySPI
     */
    @Bean("strategySPI")
    public StrategySPI strategySPI() {
        StrategySPI extension = RecExtensionLoader.getExtensionLoader(StrategySPI.class).getExtension();
        injectionSpringBeanProcessor(extension);
        return extension;
    }

    /**
     * 后置注入Bean
     *
     * @param spi spi
     */
    public void injectionSpringBeanProcessor(RecSupportSPI<?> spi) {
        SpringBean annotation = spi.getClass().getAnnotation(SpringBean.class);
        if (annotation == null) {
            return;
        }
        Class<?>[] value = annotation.value();
        Set<Class<?>> classes = Sets.newHashSet(value);
        for (Class<?> clazz : classes) {
            injection(spi, clazz);
        }
    }

    /**
     * 为SPI注入Bean
     * 
     * @param spi spi
     * @param clazz clazz
     */
    private void injection(RecSupportSPI<?> spi, Class<?> clazz) {
        try {
            Object bean = context.getBean(clazz);
            // 为spi注入
            Field[] fields = FieldUtils.getAllFields(spi.getClass());
            for (Field field : fields) {
                if (field.getType() != clazz) {
                    continue;
                }

                field.setAccessible(true);

                if (field.get(spi) == null) {
                    field.set(spi, bean);
                }
            }
        } catch (Exception e) {
            throw new InjectionBeanException(e);
        }
    }

    @Override
    public void run(String... args) throws Exception {
        // no op
    }

}
